أتقن فن بناء تطبيقات React متينة. يستكشف هذا الدليل الشامل الأنماط المتقدمة لتكوين Suspense وحدود الأخطاء، مما يتيح معالجة أخطاء متداخلة ومفصلة لتجربة مستخدم فائقة.
تكوين حدود الأخطاء (Error Boundary) و Suspense في React: نظرة عميقة في معالجة الأخطاء المتداخلة
في عالم تطوير الويب الحديث، يعد إنشاء تجربة مستخدم سلسة ومتينة أمرًا بالغ الأهمية. يتوقع المستخدمون أن تكون التطبيقات سريعة وسريعة الاستجابة ومستقرة، حتى عندما تكون ظروف الشبكة سيئة أو تحدث أخطاء غير متوقعة. توفر React، ببنيتها القائمة على المكونات، أدوات قوية لإدارة هذه التحديات: Suspense لمعالجة حالات التحميل وحدود الأخطاء (Error Boundaries) لاحتواء أخطاء وقت التشغيل. على الرغم من قوتها بشكل فردي، إلا أن إمكاناتها الحقيقية تظهر عند تكوينها معًا.
سيأخذك هذا الدليل الشامل في رحلة عميقة إلى فن تكوين React Suspense وحدود الأخطاء. سنتجاوز الأساسيات لاستكشاف الأنماط المتقدمة لمعالجة الأخطاء المتداخلة، مما يمكّنك من بناء تطبيقات لا تنجو من الأخطاء فحسب، بل تتدهور برشاقة، مع الحفاظ على الوظائف وتوفير تجربة مستخدم فائقة. سواء كنت تبني ودجت بسيطًا أو لوحة تحكم معقدة وغنية بالبيانات، فإن إتقان هذه المفاهيم سيغير بشكل أساسي كيفية تعاملك مع استقرار التطبيق وتصميم واجهة المستخدم.
الجزء الأول: إعادة النظر في اللبنات الأساسية
قبل أن نتمكن من تكوين هذه الميزات، من الضروري أن يكون لدينا فهم قوي لما تفعله كل واحدة على حدة. دعنا نجدد معرفتنا بـ React Suspense وحدود الأخطاء.
ما هو React Suspense؟
في جوهره، React.Suspense هو آلية تتيح لك "الانتظار" بشكل تصريحي لشيء ما قبل عرض شجرة المكونات الخاصة بك. حالة الاستخدام الأساسية والأكثر شيوعًا هي إدارة حالات التحميل المرتبطة بتقسيم الكود (باستخدام React.lazy) وجلب البيانات غير المتزامن.
عندما يقوم مكون داخل حدود Suspense بالتعليق (أي، يشير إلى أنه ليس جاهزًا للعرض بعد، عادةً لأنه ينتظر بيانات أو كودًا)، تتسلق React الشجرة للعثور على أقرب سلف Suspense. ثم تقوم بعرض خاصية fallback لتلك الحدود حتى يصبح المكون المعلق جاهزًا.
مثال بسيط مع تقسيم الكود:
تخيل أن لديك مكونًا كبيرًا، HeavyChartComponent، لا تريد تضمينه في حزمة جافاسكريبت الأولية. يمكنك استخدام React.lazy لتحميله عند الطلب.
// HeavyChartComponent.js
const HeavyChartComponent = () => {
// ... complex charting logic
return <div>My Detailed Chart</div>;
};
export default HeavyChartComponent;
// App.js
import React, { Suspense } from 'react';
const HeavyChartComponent = React.lazy(() => import('./HeavyChartComponent'));
function App() {
return (
<div>
<h1>My Dashboard</h1>
<Suspense fallback={<p>Loading chart...</p>}>
<HeavyChartComponent />
</Suspense>
</div>
);
}
في هذا السيناريو، سيرى المستخدم "Loading chart..." بينما يتم جلب وتحليل جافاسكريبت الخاص بـ HeavyChartComponent. بمجرد أن يصبح جاهزًا، تستبدل React بسلاسة الواجهة الاحتياطية بالمكون الفعلي.
ما هي حدود الأخطاء (Error Boundaries)؟
حدود الأخطاء (Error Boundary) هو نوع خاص من مكونات React يلتقط أخطاء جافاسكريبت في أي مكان في شجرة المكونات الفرعية الخاصة به، ويسجل تلك الأخطاء، ويعرض واجهة مستخدم احتياطية بدلاً من شجرة المكونات التي تعطلت. هذا يمنع خطأ واحد في جزء صغير من واجهة المستخدم من إسقاط التطبيق بأكمله.
من الخصائص الرئيسية لحدود الأخطاء أنها يجب أن تكون مكونات صنف (class components) وتعرف على الأقل إحدى طريقتي دورة الحياة المحددتين التاليتين:
static getDerivedStateFromError(error): تُستخدم هذه الطريقة لعرض واجهة مستخدم احتياطية بعد حدوث خطأ. يجب أن تعيد قيمة لتحديث حالة المكون.componentDidCatch(error, errorInfo): تُستخدم هذه الطريقة للآثار الجانبية، مثل تسجيل الخطأ في خدمة خارجية.
مثال كلاسيكي لحدود الأخطاء:
import React from 'react';
class MyErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// You can also log the error to an error reporting service
console.error("Uncaught error:", error, errorInfo);
// logErrorToMyService(error, errorInfo);
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
// Usage:
// <MyErrorBoundary>
// <SomeComponentThatMightThrow />
// </MyErrorBoundary>
قيود هامة: حدود الأخطاء لا تلتقط الأخطاء داخل معالجات الأحداث (event handlers)، أو الكود غير المتزامن (مثل setTimeout أو الـ promises غير المرتبطة بمرحلة العرض)، أو الأخطاء التي تحدث في مكون حدود الأخطاء نفسه.
الجزء الثاني: تآزر التكوين - لماذا يهم الترتيب
الآن بعد أن فهمنا الأجزاء الفردية، دعنا نجمعها. عند استخدام Suspense لجلب البيانات، يمكن أن يحدث شيئان: يمكن تحميل البيانات بنجاح، أو يمكن أن يفشل جلب البيانات. نحن بحاجة إلى التعامل مع كل من حالة التحميل وحالة الخطأ المحتملة.
هنا يبرز تكوين Suspense و ErrorBoundary. النمط الموصى به عالميًا هو تغليف Suspense داخل ErrorBoundary.
النمط الصحيح: ErrorBoundary > Suspense > Component
<MyErrorBoundary>
<Suspense fallback={<p>Loading...</p>}>
<DataFetchingComponent />
</Suspense>
</MyErrorBoundary>
لماذا يعمل هذا الترتيب بشكل جيد؟
دعنا نتتبع دورة حياة DataFetchingComponent:
- العرض الأولي (التعليق): يحاول
DataFetchingComponentالعرض ولكنه يجد أنه لا يملك البيانات التي يحتاجها. يقوم "بالتعليق" عن طريق إلقاء promise خاص. تلتقط React هذا الـ promise. - Suspense يتولى الأمر: تصعد React شجرة المكونات، وتجد أقرب حدود
<Suspense>، وتعرض واجهة المستخدم الاحتياطية الخاصة بهاfallback(رسالة "Loading..."). لا يتم تشغيل حدود الخطأ لأن التعليق ليس خطأ جافاسكريبت. - نجاح جلب البيانات: يتم حل الـ promise. تعيد React عرض
DataFetchingComponent، هذه المرة مع البيانات التي يحتاجها. يتم عرض المكون بنجاح، وتستبدل React الواجهة الاحتياطية لـ suspense بواجهة المستخدم الفعلية للمكون. - فشل جلب البيانات: يتم رفض الـ promise، مما يؤدي إلى إلقاء خطأ. تلتقط React هذا الخطأ أثناء مرحلة العرض.
- حدود الأخطاء تتولى الأمر: تصعد React شجرة المكونات، وتجد أقرب
<MyErrorBoundary>، وتستدعي طريقتهgetDerivedStateFromError. يقوم حدود الخطأ بتحديث حالته ويعرض واجهة المستخدم الاحتياطية الخاصة به (رسالة "Something went wrong.").
هذا التكوين يتعامل بأناقة مع كلتا الحالتين: تتم إدارة حالة التحميل بواسطة Suspense، وتتم إدارة حالة الخطأ بواسطة ErrorBoundary.
ماذا يحدث إذا عكست الترتيب؟ (Suspense > ErrorBoundary)
دعنا نفكر في النمط غير الصحيح:
<!-- Anti-Pattern: Do not do this! -->
<Suspense fallback={<p>Loading...</p>}>
<MyErrorBoundary>
<DataFetchingComponent />
</MyErrorBoundary>
</Suspense>
هذا التكوين إشكالي. عندما يقوم DataFetchingComponent بالتعليق، ستقوم حدود Suspense الخارجية بإلغاء تحميل شجرة أبنائها بالكامل — بما في ذلك MyErrorBoundary — لإظهار الواجهة الاحتياطية. إذا حدث خطأ لاحقًا، فقد يكون MyErrorBoundary الذي كان من المفترض أن يلتقطه قد تم إلغاء تحميله بالفعل، أو قد تكون حالته الداخلية (مثل `hasError`) قد فقدت. هذا يمكن أن يؤدي إلى سلوك غير متوقع ويهزم الغرض من وجود حدود مستقرة لالتقاط الأخطاء.
القاعدة الذهبية: ضع دائمًا حدود الأخطاء خارج حدود Suspense التي تدير حالة التحميل لنفس مجموعة المكونات.
الجزء الثالث: التكوين المتقدم - معالجة الأخطاء المتداخلة للتحكم الدقيق
تظهر القوة الحقيقية لهذا النمط عندما تتوقف عن التفكير في حدود خطأ واحدة على مستوى التطبيق بأكمله وتبدأ في التفكير في استراتيجية متداخلة ودقيقة. لا ينبغي لخطأ واحد في ودجت الشريط الجانبي غير الحرج أن يعطل صفحة التطبيق بأكملها. تسمح معالجة الأخطاء المتداخلة لأجزاء مختلفة من واجهة المستخدم بالفشل بشكل مستقل.
سيناريو: واجهة مستخدم لوحة تحكم معقدة
تخيل لوحة تحكم لمنصة تجارة إلكترونية. تحتوي على عدة أقسام متميزة ومستقلة:
- رأس الصفحة (Header) مع إشعارات المستخدم.
- منطقة المحتوى الرئيسية تعرض بيانات المبيعات الأخيرة.
- شريط جانبي (Sidebar) يعرض معلومات ملف المستخدم وإحصائيات سريعة.
كل قسم من هذه الأقسام يجلب بياناته الخاصة. لا ينبغي لخطأ في جلب الإشعارات أن يمنع المستخدم من رؤية بيانات مبيعاته.
النهج الساذج: حدود واحدة على المستوى الأعلى
قد يقوم المبتدئ بتغليف لوحة التحكم بأكملها في مكون ErrorBoundary و Suspense واحد.
function DashboardPage() {
return (
<MyErrorBoundary>
<Suspense fallback={<DashboardSkeleton />}>
<div className="dashboard-layout">
<HeaderNotifications />
<MainContentSales />
<SidebarProfile />
</div>
</Suspense>
</MyErrorBoundary>
);
}
المشكلة: هذه تجربة مستخدم سيئة. إذا فشلت واجهة برمجة التطبيقات (API) الخاصة بـ SidebarProfile، فإن تخطيط لوحة التحكم بالكامل يختفي ويتم استبداله بالواجهة الاحتياطية لحدود الخطأ. يفقد المستخدم الوصول إلى رأس الصفحة والمحتوى الرئيسي، على الرغم من أن بياناتهما قد تم تحميلها بنجاح.
النهج الاحترافي: حدود متداخلة ودقيقة
النهج الأفضل بكثير هو إعطاء كل قسم مستقل من واجهة المستخدم غلاف ErrorBoundary/Suspense الخاص به. هذا يعزل الإخفاقات ويحافظ على وظائف بقية التطبيق.
دعنا نعيد هيكلة لوحة التحكم الخاصة بنا بهذا النمط.
أولاً، دعنا نحدد بعض المكونات القابلة لإعادة الاستخدام ومساعدًا لجلب البيانات يتكامل مع Suspense.
// --- api.js (A simple data fetching wrapper for Suspense) ---
function wrapPromise(promise) {
let status = 'pending';
let result;
let suspender = promise.then(
(r) => {
status = 'success';
result = r;
},
(e) => {
status = 'error';
result = e;
}
);
return {
read() {
if (status === 'pending') {
throw suspender;
} else if (status === 'error') {
throw result;
} else if (status === 'success') {
return result;
}
},
};
}
export function fetchNotifications() {
console.log('Fetching notifications...');
return new Promise((resolve) => setTimeout(() => resolve(['New message', 'System update']), 2000));
}
export function fetchSalesData() {
console.log('Fetching sales data...');
return new Promise((resolve, reject) => setTimeout(() => reject(new Error('Failed to load sales data')), 3000));
}
export function fetchUserProfile() {
console.log('Fetching user profile...');
return new Promise((resolve) => setTimeout(() => resolve({ name: 'Jane Doe', level: 'Admin' }), 1500));
}
// --- Generic components for fallbacks ---
const LoadingSpinner = () => <p>Loading...</p>;
const ErrorMessage = ({ message }) => <p style={{color: 'red'}}>Error: {message}</p>;
الآن، مكونات جلب البيانات الخاصة بنا:
// --- Dashboard Components ---
import { fetchNotifications, fetchSalesData, fetchUserProfile, wrapPromise } from './api';
const notificationsResource = wrapPromise(fetchNotifications());
const salesResource = wrapPromise(fetchSalesData());
const profileResource = wrapPromise(fetchUserProfile());
const HeaderNotifications = () => {
const notifications = notificationsResource.read();
return <header>Notifications ({notifications.length})</header>;
};
const MainContentSales = () => {
const salesData = salesResource.read(); // This will throw the error
return <main>{/* Render sales charts */}</main>;
};
const SidebarProfile = () => {
const profile = profileResource.read();
return <aside>Welcome, {profile.name}</aside>;
};
أخيرًا، تكوين لوحة التحكم المتينة:
import React, { Suspense } from 'react';
import MyErrorBoundary from './MyErrorBoundary'; // Our class component from before
function DashboardPage() {
return (
<div className="dashboard-layout">
<MyErrorBoundary fallback={<header>Could not load notifications.</header>}>
<Suspense fallback={<header>Loading notifications...</header>}>
<HeaderNotifications />
</Suspense>
</MyErrorBoundary>
<MyErrorBoundary fallback={<main><p>Sales data is currently unavailable.</p></main>}>
<Suspense fallback={<main><p>Loading sales charts...</p></main>}>
<MainContentSales />
</Suspense>
</MyErrorBoundary>
<MyErrorBoundary fallback={<aside>Could not load profile.</aside>}>
<Suspense fallback={<aside>Loading profile...</aside>}>
<SidebarProfile />
</Suspense>
</MyErrorBoundary>
<div>
);
}
نتيجة التحكم الدقيق
مع هذه البنية المتداخلة، تصبح لوحة التحكم لدينا متينة بشكل لا يصدق:
- في البداية، يرى المستخدم رسائل تحميل محددة لكل قسم: "Loading notifications..."، و"Loading sales charts..."، و"Loading profile...".
- سيتم تحميل الملف الشخصي والإشعارات بنجاح وستظهر بالسرعة الخاصة بها.
- سيفشل جلب بيانات مكون
MainContentSales. بشكل حاسم، سيتم تشغيل حدود الخطأ الخاصة به فقط. - ستعرض واجهة المستخدم النهائية رأس الصفحة والشريط الجانبي المعروضين بالكامل، ولكن منطقة المحتوى الرئيسية ستعرض الرسالة: "Sales data is currently unavailable."
هذه تجربة مستخدم أفضل بكثير. يظل التطبيق فعالاً، ويفهم المستخدم بالضبط أي جزء به مشكلة، دون أن يتم حظره تمامًا.
الجزء الرابع: التحديث باستخدام الخطافات (Hooks) وتصميم واجهات احتياطية أفضل
بينما تعتبر حدود الأخطاء القائمة على الصنف (class-based Error Boundaries) هي الحل الأصلي في React، فقد طور المجتمع بدائل أكثر راحة وسهولة في الاستخدام مع الخطافات. تعد مكتبة react-error-boundary خيارًا شائعًا وقويًا.
تقديم react-error-boundary
توفر هذه المكتبة مكون <ErrorBoundary> الذي يبسط العملية ويقدم خصائص قوية مثل fallbackRender، وFallbackComponent، ورد نداء `onReset` لتنفيذ آلية "إعادة المحاولة".
دعنا نعزز مثالنا السابق بإضافة زر إعادة المحاولة إلى مكون بيانات المبيعات الفاشل.
// First, install the library:
// npm install react-error-boundary
import { ErrorBoundary } from 'react-error-boundary';
// A reusable error fallback component with a retry button
function ErrorFallback({ error, resetErrorBoundary }) {
return (
<div role="alert">
<p>Something went wrong:</p>
<pre>{error.message}</pre>
<button onClick={resetErrorBoundary}>Try again</button>
</div>
);
}
// In our DashboardPage component, we can use it like this:
function DashboardPage() {
return (
<div className="dashboard-layout">
{/* ... other components ... */}
<ErrorBoundary
FallbackComponent={ErrorFallback}
onReset={() => {
// reset the state of your query client here
// for example, with React Query: queryClient.resetQueries('sales-data')
console.log('Attempting to refetch sales data...');
}}
>
<Suspense fallback={<main><p>Loading sales charts...</p></main>}>
<MainContentSales />
</Suspense>
</ErrorBoundary>
{/* ... other components ... */}
<div>
);
}
باستخدام react-error-boundary، نحصل على عدة مزايا:
- صياغة أنظف: لا حاجة لكتابة وصيانة مكون صنف فقط لمعالجة الأخطاء.
- واجهات احتياطية قوية: تتلقى خصائص
fallbackRenderوFallbackComponentكائن `error` ودالة `resetErrorBoundary`، مما يجعل من السهل عرض معلومات خطأ مفصلة وتوفير إجراءات استرداد. - وظيفة إعادة التعيين: تتكامل خاصية `onReset` بشكل جميل مع مكتبات جلب البيانات الحديثة مثل React Query أو SWR، مما يتيح لك مسح ذاكرة التخزين المؤقت الخاصة بها وتشغيل إعادة جلب عندما ينقر المستخدم على "Try again".
تصميم واجهات احتياطية ذات معنى
تعتمد جودة تجربة المستخدم بشكل كبير على جودة واجهاتك الاحتياطية.
واجهات Suspense الاحتياطية: هياكل التحميل (Skeleton Loaders)
غالبًا ما لا تكون رسالة "Loading..." البسيطة كافية. لتجربة مستخدم أفضل، يجب أن تحاكي واجهتك الاحتياطية لـ suspense شكل وتخطيط المكون الذي يتم تحميله. يُعرف هذا بـ "هيكل التحميل" (skeleton loader). إنه يقلل من تغير التخطيط ويعطي المستخدم إحساسًا أفضل بما يمكن توقعه، مما يجعل وقت التحميل يبدو أقصر.
const SalesChartSkeleton = () => (
<div className="skeleton-wrapper">
<div className="skeleton-title"></div>
<div className="skeleton-chart-area"></div>
</div>
);
// Usage:
<Suspense fallback={<SalesChartSkeleton />}>
<MainContentSales />
</Suspense>
واجهات الأخطاء الاحتياطية: قابلة للتنفيذ ومتعاطفة
يجب أن تكون واجهة الخطأ الاحتياطية أكثر من مجرد رسالة فظة "Something went wrong." يجب أن تكون واجهة الخطأ الاحتياطية الجيدة:
- متعاطفة: تعترف بإحباط المستخدم بنبرة ودية.
- غنية بالمعلومات: تشرح بإيجاز ما حدث بمصطلحات غير تقنية، إن أمكن.
- قابلة للتنفيذ: توفر طريقة للمستخدم للتعافي، مثل زر "إعادة المحاولة" لأخطاء الشبكة المؤقتة أو رابط "اتصل بالدعم" للإخفاقات الحرجة.
- تحافظ على السياق: كلما أمكن، يجب احتواء الخطأ داخل حدود المكون، وألا يسيطر على الشاشة بأكملها. يحقق نمطنا المتداخل هذا بشكل مثالي.
الجزء الخامس: أفضل الممارسات والأخطاء الشائعة
أثناء تنفيذ هذه الأنماط، ضع في اعتبارك أفضل الممارسات والمزالق المحتملة التالية.
قائمة أفضل الممارسات
- ضع الحدود عند الفواصل المنطقية لواجهة المستخدم: لا تغلف كل مكون على حدة. ضع أزواج
ErrorBoundary/Suspenseحول الوحدات المنطقية والمستقلة لواجهة المستخدم، مثل المسارات (routes)، أو أقسام التخطيط (رأس الصفحة، الشريط الجانبي)، أو الودجت المعقدة. - سجل أخطائك: الواجهة الاحتياطية التي يراها المستخدم هي نصف الحل فقط. استخدم `componentDidCatch` أو رد نداء في `react-error-boundary` لإرسال معلومات خطأ مفصلة إلى خدمة تسجيل (مثل Sentry أو LogRocket أو Datadog). هذا أمر بالغ الأهمية لتصحيح المشكلات في الإنتاج.
- نفذ استراتيجية إعادة التعيين/إعادة المحاولة: معظم أخطاء تطبيقات الويب عابرة (على سبيل المثال، فشل الشبكة المؤقت). امنح المستخدمين دائمًا طريقة لإعادة محاولة العملية الفاشلة.
- حافظ على بساطة الحدود: يجب أن يكون حدود الخطأ نفسه بسيطًا قدر الإمكان ومن غير المرجح أن يلقي خطأً خاصًا به. وظيفته الوحيدة هي عرض واجهة احتياطية أو الأبناء.
- اجمعها مع الميزات المتزامنة (Concurrent Features): لتجربة أكثر سلاسة، استخدم ميزات مثل `startTransition` لمنع ظهور واجهات التحميل الاحتياطية المزعجة لعمليات جلب البيانات السريعة جدًا، مما يسمح لواجهة المستخدم بالبقاء تفاعلية أثناء تحضير المحتوى الجديد في الخلفية.
الأخطاء الشائعة التي يجب تجنبها
- النمط المضاد للترتيب المعكوس: كما تمت مناقشته، لا تضع أبدًا
SuspenseخارجErrorBoundaryمخصص للتعامل مع أخطائه. سيؤدي هذا إلى فقدان الحالة وسلوك غير متوقع. - الاعتماد على الحدود لكل شيء: تذكر، حدود الأخطاء تلتقط الأخطاء فقط أثناء العرض، وفي طرق دورة الحياة، وفي منشئات الشجرة بأكملها أسفلها. إنها لا تلتقط الأخطاء في معالجات الأحداث. لا يزال يتعين عليك استخدام كتل
try...catchالتقليدية للأخطاء في الكود الحتمي. - التداخل المفرط: على الرغم من أن التحكم الدقيق جيد، إلا أن تغليف كل مكون صغير في حدوده الخاصة هو إفراط ويمكن أن يجعل شجرة المكونات الخاصة بك صعبة القراءة والتصحيح. ابحث عن التوازن الصحيح بناءً على الفصل المنطقي للمسؤوليات في واجهة المستخدم الخاصة بك.
- الواجهات الاحتياطية العامة: تجنب استخدام نفس رسالة الخطأ العامة في كل مكان. صمم واجهات الخطأ والتحميل الاحتياطية لتناسب السياق المحدد للمكون. يجب أن تبدو حالة التحميل لمعرض الصور مختلفة عن حالة التحميل لجدول البيانات.
function MyComponent() {
const handleClick = async () => {
try {
await sendDataToApi();
} catch (error) {
// This error will NOT be caught by an Error Boundary
showErrorToast('Failed to save data');
}
};
return <button onClick={handleClick}>Save</button>;
}
الخاتمة: البناء من أجل المتانة
يعد إتقان تكوين React Suspense وحدود الأخطاء خطوة مهمة نحو أن تصبح مطور React أكثر نضجًا وفعالية. إنه يمثل تحولًا في العقلية من مجرد منع تعطل التطبيقات إلى هندسة تجربة متينة حقًا ومتمحورة حول المستخدم.
من خلال تجاوز معالج الأخطاء الوحيد على المستوى الأعلى واعتماد نهج متداخل ودقيق، يمكنك بناء تطبيقات تتدهور برشاقة. يمكن أن تفشل الميزات الفردية دون تعطيل رحلة المستخدم بأكملها، وتصبح حالات التحميل أقل تدخلاً، ويتم تمكين المستخدمين بخيارات قابلة للتنفيذ عند حدوث أخطاء. هذا المستوى من المتانة وتصميم تجربة المستخدم المدروس هو ما يفصل التطبيقات الجيدة عن التطبيقات الرائعة في المشهد الرقمي التنافسي اليوم. ابدأ في التكوين، وابدأ في التداخل، وابدأ في بناء تطبيقات React أكثر قوة اليوم.